////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.styles
{

import flash.utils.describeType;
import mx.core.mx_internal;
import mx.managers.SystemManager;

use namespace mx_internal;

/**
 *  The StyleManager class manages the following:
 *  <ul>
 *    <li>Which CSS style properties the class inherits</li>
 *    <li>Which style properties are colors, and therefore get special handling</li>
 *    <li>A list of strings that are aliases for color values</li>
 *  </ul>
 *
 *  @see mx.styles.CSSStyleDeclaration
 */
public class StyleManager
{
	include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class constants
	//
	//--------------------------------------------------------------------------

	/**
	 *	The <code>getColorName()</code> method returns this value if the passed-in
	 *  String is not a legitimate color name.
	 */
	public static const NOT_A_COLOR:uint = 0xFFFFFFFF;
	
	/**
	 *  @private
	 *  Set of inheriting non-color styles.
	 *  This is not the complete set from CSS.
	 *  Some of the omitted we don't support at all,
	 *  others may be added later as needed.
	 *  The <code>isInheritingTextFormatStyle()</code> method queries this set.
	 */
	private static var inheritingTextFormatStyles:Object =
	{
		align: true,
		bold: true,
		color: true,
		font: true,
		indent: true,
		italic: true,
		size: true
	};
	
	//--------------------------------------------------------------------------
	//
	//  Class variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *	A map of CSS selectors -- such as "global", "Button", and ".bigRed" --
	 *  to CSSStyleDeclarations.
	 *  This collection is accessed via getStyleDeclaration(),
	 *  setStyleDeclaration(), and clearStyleDeclaration().
	 */
	private static var selectors:Object = {};

	/**
	 *  @private
	 *  The root of all proto chains used for looking up styles.
	 *  This object is initialized once by initProtoChainRoots() and
	 *  then updated by calls to setStyle() on the global CSSStyleDeclaration.
	 *  It is accessed by code that needs to construct proto chains,
	 *  such as the initProtoChain() method of UIComponent.
	 */
	mx_internal static var stylesRoot:Object;
	
	/**
	 *  @private
	 *  Set of inheriting non-color styles.
	 *  This is not the complete set from CSS. 
	 *  Some of the omitted we don't support at all,
	 *  others may be added later as needed.
	 *  The method registerInheritingStyle() adds to this set
	 *  and isInheritingStyle() queries this set.
	 */
	mx_internal static var inheritingStyles:Object = {};
	
	/**
	 *  @private
	 *  Set of styles for which setStyle() causes
	 *  invalidateSize() to be called on the component.
	 *  The method registerSizeInvalidatingStyle() adds to this set
	 *  and isSizeInvalidatingStyle() queries this set.
	 */
	private static var sizeInvalidatingStyles:Object =
	{
		borderStyle: true,
		borderThickness: true,
		fontAntiAliasType: true,
		fontFamily: true,
		fontGridFitType: true,
		fontSharpness: true,
		fontSize: true,
		fontStyle: true,
		fontThickness: true,
		fontWeight: true,
		headerHeight: true,
		horizontalAlign: true,
		horizontalGap: true,
		leading: true,
		paddingBottom: true,
		paddingLeft: true,
		paddingRight: true,
		paddingTop: true,
		strokeWidth: true,
		tabHeight: true,
		tabWidth: true,
		verticalAlign: true,
		verticalGap: true
	}

	/**
	 *  @private
	 *  Set of styles for which setStyle() causes
	 *  invalidateSize() to be called on the component's parent.
	 *  The method registerParentSizeInvalidatingStyle() adds to this set
	 *  and isParentSizeInvalidatingStyle() queries this set.
	 */
	private static var parentSizeInvalidatingStyles:Object =
	{
		bottom: true,
		horizontalCenter: true,
		left: true,
		right: true,
		top: true,
		verticalCenter: true
	}

	/**
	 *  @private
	 *  Set of styles for which setStyle() causes
	 *  invalidateDisplayList() to be called on the component's parent.
	 *  The method registerParentDisplayListInvalidatingStyle() adds to this set
	 *  and isParentDisplayListInvalidatingStyle() queries this set.
	 */
	private static var parentDisplayListInvalidatingStyles:Object =
	{
		bottom: true,
		horizontalCenter: true,
		left: true,
		right: true,
		top: true,
		verticalCenter: true
	}

	/**
	 *  @private
	 *  Set of color names. 
	 *  The method registerColorName() adds to this set
	 *  and isColorName() queries this set.
	 *  All color names in this set are lowercase in order to support
	 *  case-insensitive mapping in the StyleManager methods getColorName(),
	 *  getColorNames(), registerColorName(), and isColorName().
	 *  We handle color names at runtime in a case-insensitive way
	 *  because the MXML compiler does this at compile time,
	 *  in conformance with the CSS spec.
	 */
	private static var colorNames:Object =
	{
		transparent: "transparent",
		black: 0x000000,
		blue: 0x0000FF,
		green: 0x008000,
		gray: 0x808080,
		silver: 0xC0C0C0,
		lime: 0x00FF00,
		olive: 0x808000,
		white: 0xFFFFFF,
		yellow: 0xFFFF00,
		maroon: 0x800000,
		navy: 0x000080,
		red: 0xFF0000,
		purple: 0x800080,
		teal: 0x008080,
		fuchsia: 0xFF00FF,
		aqua: 0x00FFFF,
		magenta: 0xFF00FF,
		cyan: 0x00FFFF,
		
		// IMPORTANT: Theme colors must also be updated
		// in the Flex compiler's CSS parser
		// (in \src\java\macromedia\css\Descriptor.java)
		// and possibly other places as well. Grep for them!
		halogreen: 0x80FF4D,
		haloblue: 0x009DFF,
		haloorange: 0xFFB600,
		halosilver: 0xAECAD9
	};

	/**
	 *  @private
	 */
	mx_internal static var typeSelectorCache:Object = {};

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *  This method is called by code autogenerated by the MXML compiler,
	 *  after StyleManager.styles is popuplated with CSSStyleDeclarations.
	 */
	mx_internal static function initProtoChainRoots():void
	{
		if (!stylesRoot)
			stylesRoot = selectors["global"].createProtoChainRoot();
	}

	/**
	 *  Gets the CSSStyleDeclaration object that stores the rules
	 *  for the specified CSS selector.
	 *
	 *  <p>If the <code>selector</code> parameter starts with a period (.),
	 *  the returned CSSStyleDeclaration is a class selector and applies only to those instances
	 *  whose <code>styleName</code> property specifies that selector
	 *  (not including the period).
	 *  For example, the class selector <code>".bigMargins"</code>
	 *  applies to any UIComponent whose <code>styleName</code>
	 *  is <code>"bigMargins"</code>.</p>
	 *
	 *  <p>If the <code>selector</code> parameter does not start with a period,
	 *  the returned CSSStyleDeclaration is a type selector and applies to all instances
	 *  of that type.
	 *  For example, the type selector <code>"Button"</code>
	 *  applies to all instances of Button and its subclasses.</p>
	 *
	 *  <p>The <code>global</code> selector is similar to a type selector
	 *  and does not start with a period.</p>
	 * 
	 *  @param selector The name of the CSS selector.
	 * 
	 *  @return The style declaration whose name matches the <code>selector</code> property.
	 */
	public static function getStyleDeclaration(
								selector:String):CSSStyleDeclaration
	{
		// If we were passed a type selector with a package name,
		// such as "mx.controls.Button", strip off the package name
		// leaving just "Button" and look for that selector.
		if (selector.charAt(0) != ".")
		{
			var index:int = selector.lastIndexOf(".");
			if (index != -1)
				selector = selector.substr(index + 1);
		}

		return selectors[selector];
	}
	
	/**
	 *  Sets the CSSStyleDeclaration object that stores the rules
	 *  for the specified CSS selector.
	 *
	 *  <p>If the <code>selector</code> parameter starts with a period (.),
	 *  the specified selector is a class selector and applies only to those instances
	 *  whose <code>styleName</code> property specifies that selector
	 *  (not including the period).
	 *  For example, the class selector <code>".bigMargins"</code>
	 *  applies to any UIComponent whose <code>styleName</code>
	 *  is <code>"bigMargins"</code>.</p>
	 *
	 *  <p>If the <code>selector</code> parameter does not start with a period,
	 *  the specified selector is a "type selector" and applies to all instances
	 *  of that type.
	 *  For example, the type selector <code>"Button"</code>
	 *  applies to all instances of Button and its subclasses.</p>
	 *
	 *  <p>The <code>global</code> selector is similar to a type selector
	 *  and does not start with a period.</p>
	 *  
	 *  @param selector The name of the CSS selector.
	 *  @param styleDeclaration The new style declaration.
	 *  @param update Set to <code>true</code> to force an immediate update of the styles. 
	 *  Set to <code>false</code> to avoid an immediate update of the styles in the application. 
	 *  The styles will be updated the next time this method or the <code>clearStyleDeclaration()</code> method 
	 *  is called with the <code>update</code> property set to <code>true</code>.
	 */
	public static function setStyleDeclaration(
								selector:String,
								styleDeclaration:CSSStyleDeclaration,
								update:Boolean):void
	{
		styleDeclaration.selectorRefCount++;
		
		selectors[selector] = styleDeclaration;
	
		// flush cache and start over
		typeSelectorCache = new Object();

		if (update)
			styleDeclarationsChanged();
	}
	
	/**
	 *  Clears the CSSStyleDeclaration object that stores the rules
	 *  for the specified CSS selector.
	 *
	 *  <p>If the specified selector is a class selector (for example, ".bigMargins" or ".myStyle"), 
	 *  you must be sure to start the 
	 *  <code>selector</code> property with a period (.).</p>
	 *
	 *  <p>If the specified selector is a type selector (for example, "Button"), do not start the
	 *  <code>selector</code> property with a period.</p>
	 *
	 *  <p>The <code>global</code> selector is similar to a type selector
	 *  and does not start with a period.</p>
	 *  
	 *  @param selector The name of the CSS selector to clear.
	 *  @param update Set to <code>true</code> to force an immediate update of the styles. 
	 *  Set to <code>false</code> to avoid an immediate update of the styles in the application. 
	 *  The styles will be updated the next time this method or the <code>setStyleDeclaration()</code> method is 
	 *  called with the <code>update</code> property set to <code>true</code>.
	 */
	public static function clearStyleDeclaration(selector:String,
												 update:Boolean):void
	{
		var styleDeclaration:CSSStyleDeclaration =
			getStyleDeclaration(selector);

		if (styleDeclaration && styleDeclaration.selectorRefCount > 0)
			styleDeclaration.selectorRefCount--;

		delete selectors[selector];
		
		if (update)
			styleDeclarationsChanged();
	}

	/**
	 *  @private
	 *  After an entire selector is added, replaced, or removed,
	 *  this method updates all the DisplayList trees.
	 */
	private static function styleDeclarationsChanged():void
	{
		var sms:Array /* of SystemManager */ =
			SystemManager.topLevelSystemManagers;
		var n:int = sms.length;
		for (var i:int = 0; i < n; i++)
		{
			var sm:SystemManager = SystemManager(sms[i]);
			sm.regenerateStyleCache(true);
			sm.notifyStyleChangeInChildren(null, true);
		}
	}
	
	/**
	 *  Adds to the list of styles that can inherit values
	 *  from their parents.
	 *  
	 *  <p><b>Note:</b> Ensure that you avoid using duplicate style names, as name 
	 *  collisions can result in decreased performance if a style that is
	 *  already used becomes inheriting.</p>
	 *
	 *  @param styleName The name of the style that is added to the list of styles that can inherit values.
	 */
	public static function registerInheritingStyle(styleName:String):void
	{
		StyleManager.inheritingStyles[styleName] = true;
	}

	/**
	 *  Tests to see if a style is inheriting.
	 *
	 *  @param styleName The name of the style that you test to see if it is inheriting.
	 *
	 *  @return Returns <code>true</code> if the specified style is inheriting.
	 */
	public static function isInheritingStyle(styleName:String):Boolean
	{
		return StyleManager.inheritingStyles[styleName] == true;
	}

	/**
	 *  Test to see if a TextFormat style is inheriting.
	 *
	 *  @param styleName The name of the style that you test to see if it is inheriting.
	 *
	 *  @return Returns <code>true</code> if the specified TextFormat style
	 *  is inheriting.
	 */
	public static function isInheritingTextFormatStyle(styleName:String):Boolean
	{
		return StyleManager.inheritingTextFormatStyles[styleName] == true;
	}

	/**
	 *  Adds to the list of styles which may affect the measured size
	 *  of the component.
	 *  When one of these styles is set with <code>setStyle()</code>,
	 *  the <code>invalidateSize()</code> method is automatically called on the component
	 *  to make its measured size get recalculated later.
	 *
	 *  @param styleName The name of the style that you add to the list.
	 */	
	public static function registerSizeInvalidatingStyle(styleName:String):void
	{
		StyleManager.sizeInvalidatingStyles[styleName] = true;
	}

	/**
	 *  Tests to see if a style changes the size of a component.
	 *  
	 *  <p>When one of these styles is set with the <code>setStyle()</code> method,
	 *  the <code>invalidateSize()</code> method is automatically called on the component
	 *  to make its measured size get recalculated later.</p>
	 *
	 *  @param styleName The name of the style to test.
	 *
	 *  @return Returns <code>true</code> if the specified style is one
	 *  which may affect the measured size of the component.
	 */
	public static function isSizeInvalidatingStyle(styleName:String):Boolean
	{
		return StyleManager.sizeInvalidatingStyles[styleName] == true;
	}
	
	/**
	 *  Adds to the list of styles which may affect the measured size
	 *  of the component's parent container.
	 *  <p>When one of these styles is set with <code>setStyle()</code>,
	 *  the <code>invalidateSize()</code> method is automatically called on the component's
	 *  parent container to make its measured size get recalculated
	 *  later.</p>
	 *
	 *  @param styleName The name of the style to register.
	 */	
	public static function registerParentSizeInvalidatingStyle(styleName:String):void
	{
		StyleManager.parentSizeInvalidatingStyles[styleName] = true;
	}

	/**
	 *  Tests to see if the style changes the size of the component's parent container.
	 *  
	 *  <p>When one of these styles is set with <code>setStyle()</code>,
	 *  the <code>invalidateSize()</code> method is automatically called on the component's
	 *  parent container to make its measured size get recalculated
	 *  later.</p>
	 *
	 *  @param styleName The name of the style to test.
	 *
	 *  @return Returns <code>true</code> if the specified style is one
	 *  which may affect the measured size of the component's
	 *  parent container.
	 */
	public static function isParentSizeInvalidatingStyle(styleName:String):Boolean
	{
		return StyleManager.parentSizeInvalidatingStyles[styleName] == true;
	}
	
	/**
	 *  Adds to the list of styles which may affect the appearance
	 *  or layout of the component's parent container.
	 *  When one of these styles is set with <code>setStyle()</code>,
	 *  the <code>invalidateDisplayList()</code> method is auomatically called on the component's
	 *  parent container to make it redraw and/or relayout its children.
	 *
	 *  @param styleName The name of the style to register.
	 */	
	public static function registerParentDisplayListInvalidatingStyle(
								styleName:String):void
	{
		StyleManager.parentDisplayListInvalidatingStyles[styleName] = true;
	}

	/**
	 *  Tests to see if this style affects the component's parent container in 
	 *  such a way as to require that the parent container redraws itself when this style changes.
	 *  
	 *  <p>When one of these styles is set with <code>setStyle()</code>,
	 *  the <code>invalidateDisplayList()</code> method is auomatically called on the component's
	 *  parent container to make it redraw and/or relayout its children.</p>
	 *
	 *  @param styleName The name of the style to test.
	 *
	 *  @return Returns <code>true</code> if the specified style is one
	 *  which may affect the appearance or layout of the component's
	 *  parent container.
	 */
	public static function isParentDisplayListInvalidatingStyle(
								styleName:String):Boolean
	{
		return StyleManager.parentDisplayListInvalidatingStyles[styleName] == true;
	}
	
	/**
	 *  Adds a color name to the list of aliases for colors.
	 *
	 *  @param colorName The name of the color to add to the list; for example, "blue".
	 *  If you later access this color name, the value is not case-sensitive.
	 *
	 *  @param colorValue Color value, for example, 0x0000FF.
	 */
	public static function registerColorName(colorName:String, colorValue:uint):void
	{
		StyleManager.colorNames[colorName.toLowerCase()] = colorValue;
	}

	/**
	 *  Tests to see if the given String is an alias for a color value. For example, 
	 *  by default, the String "blue" is an alias for 0x0000FF.
	 *
	 *  @param colorName The color name to test. This parameter is not case-sensitive.
	 *
	 *  @return Returns <code>true</code> if <code>colorName</code> is an alias
	 *  for a color.
	 */
	public static function isColorName(colorName:String):Boolean
	{
		return StyleManager.colorNames[colorName.toLowerCase()] !== undefined;
	}

	/**
	 *  Returns the numeric RGB color value that corresponds to the
	 *  specified color string.
	 *  The color string can be either a case-insensitive color name
	 *  such as <code>"red"</code>, <code>"Blue"</code>, or
	 *  <code>"haloGreen"</code>, a hexadecimal value such as 0xFF0000, or a #-hexadecimal String
	 *  such as <code>"#FF0000"</code>.
	 *  
	 *  <p>This method returns a uint, such as 4521830, that represents a color. You can convert
	 *  this uint to a hexadecimal value by passing the numeric base (in this case, 16), to 
	 *  the uint class's <code>toString()</code> method, as the following example shows:</p>
	 *  <pre>
	 *  import mx.styles.StyleManager;
	 *  private function getNewColorName():void {
	 *      StyleManager.registerColorName("soylentGreen",0x44FF66);
	 *      trace(StyleManager.getColorName("soylentGreen").toString(16));
	 *  }
	 *  </pre>
	 *
	 *  @param colorName The color name.
	 *  
	 *  @return Returns a uint that represents the color value or <code>NOT_A_COLOR</code>
	 *  if the value of the <code>colorName</code> property is not an alias for a color.
	 */
	public static function getColorName(colorName:Object):uint
	{
		var n:Number;

		if (colorName is String)
		{
			if (colorName.charAt(0) == "#")
			{
				// Map "#77EE11" to 0x77EE11
				n = Number("0x" + colorName.slice(1));
				return isNaN(n) ? NOT_A_COLOR : uint(n);
			}

			if (colorName.charAt(1) == "x" && colorName.charAt(0) == '0')
			{
				// Map "#77EE11" to 0x77EE11
				 n = Number(colorName);
				return isNaN(n) ? NOT_A_COLOR : uint(n);
			}

			// Map "red" or "Red" to 0xFF0000;
			// Map "haloGreen" or "HaLoGrEeN" to 0x46FF00.
			var c:* = StyleManager.colorNames[colorName.toLowerCase()];
			if (c === undefined)
				return NOT_A_COLOR;

			return uint(c);
		}

		return uint(colorName);
	}

	/**
	 *  Converts each element of the colors Array from a color name
	 *  to a numeric RGB color value.
	 *  Each color String can be either a case-insensitive color name
	 *  such as <code>"red"</code>, <code>"Blue"</code>, or
	 *  <code>"haloGreen"</code>, a hexadecimal value such as 0xFF0000, or a #-hexadecimal String
	 *  such as <code>"#FF0000"</code>..
	 *
	 *  @param colors An Array of color names.
	 */
	public static function getColorNames(colors:Array /* of Number or String */):void
	{
        if (!colors)
			return;

		var n:int = colors.length;
		for (var i:int = 0; i < n; i++)
        {
            if ((colors[i] != null) && isNaN(colors[i]))
            {
                var colorNumber:uint = StyleManager.getColorName(colors[i]);
                if (colorNumber != NOT_A_COLOR)
                    colors[i] = colorNumber;
            }
        }
	}

	/**
	 *  Determines if a specified parameter is a valid style property. For example:
	 *  
	 *  <pre>
	 *  trace(StyleManager.isValidStyleValue(myButton.getStyle("color")).toString());
	 *  </pre>
	 *  
	 *  <p>This can be useful because some styles can be set to values
	 *  such as 0, <code>NaN</code>,
	 *  the empty String (<code>""</code>), or <code>null</code>, which can
	 *  cause an <code>if (value)</code> test to fail.</p>
	 *  
	 *  @param value The style property to test.
	 *  
	 *  @return If you pass the value returned by a <code>getStyle()</code> method call
	 *  to this method, it returns <code>true</code> if the style
	 *  was set and <code>false</code> if it was not set.
	 *

	 */
	public static function isValidStyleValue(value:*):Boolean
	{
		// By convention, we don't allow style values to be undefined,
		// so we can check for this as the "not set" value.
		return value !== undefined;
	}
}

}
